﻿// <copyright file="Area.cs" company="City of Portland, BES-ASM">
// </copyright>
// <summary>Area class</summary>

namespace BESASM.EMGAATS.BusinessClasses
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;

  /// <summary>
  /// Manages impervious areas
  /// </summary>
  public class Area : Entity, IArea
  {
    private int areaSqFt = 0;
    private double sanitaryFlowCfs = 0d;
    private IPolygon geometry;
    private AreaType areaType = AreaType.UNKN;
    private double flowLengthFt;
    private double slopeFtPerFt;
    private INode overlandFlowDest;

    /// <summary>
    /// Initializes a new instance of the <see cref="Area"/> class.
    /// </summary>
    /// <param name="id">The ID of the Area</param>
    public Area(int id)
      : base(EntityType.Area, id)
    {
    }

    /// <summary>
    /// Gets or sets the polygon geometry representing this area
    /// </summary>
    public IPolygon Geometry
    {
      get
      {
        return this.geometry;
      }

      set
      {
        if (value != this.geometry)
        {
          this.geometry = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets or sets the type of area (e.g., building, parking, etc.)
    /// </summary>
    public AreaType AreaType
    {
      get
      {
        return this.areaType;
      }

      set
      {
        if (value != this.areaType)
        {
          this.areaType = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets or sets the area represented, in square feet
    /// </summary>
    public int AreaSqFt
    {
      get
      {
        return this.areaSqFt;
      }

      set
      {
        if (value != this.areaSqFt)
        {
          this.areaSqFt = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets or sets he sanitary flow generate by the area, in cfs
    /// </summary>
    public double SanitaryFlowCfs
    {
      get
      {
        return this.sanitaryFlowCfs;
      }

      set
      {
        if (value != this.sanitaryFlowCfs)
        {
          this.sanitaryFlowCfs = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets or sets the flow length for this area in feet
    /// </summary>
    public double FlowLengthFt
    {
      get
      {
        return this.flowLengthFt;
      }

      set
      {
        if (value != this.flowLengthFt)
        {
          this.flowLengthFt = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets or sets the slope for this area as rise over run ratio (ft/ft)
    /// </summary>
    public double SlopeFtPerFt
    {
      get
      {
        return this.slopeFtPerFt;
      }

      set
      {
        if (value != this.slopeFtPerFt)
        {
          this.slopeFtPerFt = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets the amount of undirected area
    /// </summary>
    public int RemainingAreaSqFt
    {
      get
      {
        return (int)Math.Max(0, this.areaSqFt - this.GetSumDirectorAreas());
      }
    }

    /// <summary>
    /// Gets a list of  directors and the corresponding amount of area in excess of the available area
    /// </summary>
    public List<ExcessDirectorItem> ExcessDirectorList
    {
      get
      {
        List<ExcessDirectorItem> excesses = new List<ExcessDirectorItem>();
        int accumulatedAllocation = 0;
        IList<IDirector> directors = this.Directors;
        for (int i = 0; i < directors.Count; i++)
        {
          int remainingAreaSqFt = (int)Math.Max(0, this.areaSqFt - accumulatedAllocation);
          int overageSqFt = 0;
          if (remainingAreaSqFt < directors[i].AreaSqFt)
          {
            overageSqFt = directors[i].AreaSqFt - remainingAreaSqFt;
            excesses.Add(new ExcessDirectorItem(directors[i], overageSqFt));
          }

          accumulatedAllocation += directors[i].AreaSqFt;
        }

        return excesses;
      }
    }

    /// <summary>
    /// Gets a value indicating whether directors have over-allocated area
    /// </summary>
    public bool ExcessAreaAllocated
    {
      get
      {
        return (this.AreaSqFt - this.GetSumDirectorAreas()) < 0.0;
      }
    }

    /// <summary>
    /// Gets a value indicating whether the Area has a Director
    /// </summary>
    public bool HasDirector
    {
      get
      {
        return this.Directors.Count > 0;
      }
    }

    /// <summary>
    /// Gets or sets the default node where overland flow from the Area will drain to
    /// </summary>
    public INode OverlandFlowDestination
    {
      get
      {
        return this.overlandFlowDest;
      }

      set
      {
        if (value != this.overlandFlowDest)
        {
          this.overlandFlowDest = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets the list of Directors assigned to the Area
    /// </summary>
    public IList<IDirector> Directors
    {
      get
      {
        Network network = this.Network as Network;
        if (network != null)
        {
          return network.GetAreaDirectors(this).ToList().AsReadOnly();
        }
        else
        {
          if (this.Network != null)
          {
            return this.Network.Directors.Where(director => director.Area == this).ToList().AsReadOnly();
          }
          else
          {
            return new List<IDirector>().AsReadOnly();
          }
        }
      }
    }

    /// <summary>
    /// Adds a director reference to the Area
    /// </summary>
    /// <param name="director">The director to be added</param>
    public void AddDirector(IDirector director)
    {
      if (director.Network == null || director.Network != this.Network)
      {
        throw new InvalidOperationException("Cannot add directors to areas that are not part of the same network.");
      }

      director.Area = this;
    }

    /// <summary>
    /// Removes a director reference from the Area
    /// </summary>
    /// <param name="director">The director to remove</param>
    public void RemoveDirector(IDirector director)
    {
      if (director.Area != this)
      {
        throw new ArgumentException("Cannot remove director, because this area does not contain the specified director.");
      }
      else
      {
        director.Area = null;
      }
    }

    /// <summary>
    /// Creates a new entity that is a copy of the current instance.
    /// </summary>
    /// <returns>A new object that is a shallow copy of this instance.</returns>
    public override object Clone()
    {
      Area clone = new Area(this.Id);

      // Simple properties and references
      // DO NOT copy Network, clones must be added to a network
      clone.AreaSqFt = this.AreaSqFt;
      clone.AreaType = this.AreaType;
      clone.OverlandFlowDestination = this.OverlandFlowDestination;
      clone.SanitaryFlowCfs = this.SanitaryFlowCfs;

      // Geometry (copy)
      if (this.Geometry != null)
      {
        clone.Geometry = this.Geometry.Clone() as IPolygon;
      }

      this.CloneAuditProperties(clone);

      ////// Directors
      ////foreach (IDirector director in this.Directors)
      ////{
      ////  clone.AddDirector(director);
      ////}

      clone.SetModified(false);
      return clone;
    }

    /// <summary>
    /// Returns a string representation of this area.
    /// </summary>
    /// <returns>A string representation of this area.</returns>
    public override string ToString()
    {
      return string.Format("A{0} (AreaSqFt: {1})", this.Id, this.AreaSqFt);
    }

    private double GetSumDirectorAreas()
    {
      var sumDirectorAreas = (from d in this.Directors
                              select d.AreaSqFt).Sum();
      return sumDirectorAreas;
    }
  }
}
